<!doctype html>
<html lang="zh">
  <head>
    <meta charset="UTF-8">
    <title>中文编程</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      button {
        position: relative;
        padding: 10px 40px;
        margin: 20px 10px 10px 0;
        float: left;
        border-radius: 3px;
        font-size: 20px;
        color: #ffffff;
        text-decoration: none;
        background-color: #2ecc71;
        border: none;
        border-bottom: 5px solid #27ae60;
        text-shadow: 0 -2px #27ae60;
        transition: all 0.1s;
        cursor: pointer;
      }

      button:hover,
      button:active {
        transform: translate(0px, 5px);
        border-bottom: 1px solid #2ecc71;
      }

      h1 {
        margin-bottom: 50px;
      }

      textarea {
        margin: 10px 0;
        padding: 10px;
        border: 1px black solid;
        border-radius: 10px;
        resize: none;
        background: rgba(255, 255, 255, 0.25);
      }

      textarea:focus-visible {
        outline: 1px solid black;
      }

      .box {
        position: absolute;
        top: 0;
        width: 100vw;
        min-height: 100vh;
        padding: 100px 0;
        display: flex;
        justify-content: center;
        flex-wrap: wrap;
        align-items: center;
        flex-direction: column;
        background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
      }

      .info {
        display: flex;
        margin-top: 100px;
      }

      .info-label {
        display: flex;
        flex-direction: column;
      }
    </style>
  </head>
  <body>

    <div class="box">
      <h1>中文编程</h1>
      <div class="info-label">
        <label for="code">Code:</label>
        <textarea id="code" cols="110" rows="25"></textarea>
      </div>
      <button onclick="运行(document.querySelector('#code').value)">Run！</button>

      <div class="info">
        <div class="info-label">
          <label for="console">Console:</label>
          <textarea id="console" cols="50" rows="10"></textarea>
        </div>

        <div style="width: 30px;"></div>

        <div class="info-label">
          <label for="ast">AST:</label>
          <textarea id="ast" cols="50" rows="10"></textarea>
        </div>
      </div>
    </div>

    <script>
      document.querySelector('#code').value = '~ 这里不会被执行\n~ 这里不会被执行\n\n我有一个字符串「🐔」，等于“坤坤❤️”。\n我有一个字符串「🏀」，等于“打篮球”。\n\n弹窗：“我叫”「🐔」“，我会”「🏀」“。”。\n弹窗：「🐔」。\n\n\n我有一个整数「民族」，等于五十六。\n控制台输出：民族“个民族”。'

      function 运行(脚本) {
        const 输入流 = 脚本 => {
          let 位置 = 0, 行 = 1, 列 = 0

          const 下个字符 = () => {
            const 字符 = 脚本.charAt(位置++)
            if (字符 === '\n') {
              行++
              列 = 0
            } else 列++
            return 字符
          }

          const 观测 = () => 脚本.charAt(位置)

          const 结束 = () => 位置 >= 脚本.length

          const 异常 = 信息 => {
            throw new Error(`${信息} ${行}:${列}`)
          }

          return {下个字符, 观测, 结束, 异常}
        }

        const 令牌流 = 输入流 => {
          const 关键词 = /(我|有|一|个)/i
          const 类型 = /(整|数|字|符|串)/i
          const 空白字符 = /(，| |\t|\n)/i
          const 整数 = /[0-9一二三四五六七八九十壹贰叁肆伍陆柒捌玖拾]/i
          const 变量 = /(?!我|有|一|个)[\u4e00-\u9fa5]/i
          const 标点符号 = /(。|：|「|」)/i
          const 运算符 = /(等|于)/i
          let 当前 = null

          const 是否 = {
            关键词: 字符 => 关键词.test(字符),
            类型: 字符 => 类型.test(字符),
            空白字符: 字符 => 空白字符.test(字符),
            整数: 字符 => 整数.test(字符),
            变量: 字符 => 变量.test(字符),
            标点符号: 字符 => 标点符号.test(字符),
            运算符: 字符 => 运算符.test(字符)
          }

          const 跳过 = {
            批注: () => {
              读取.期间(字符 => 字符 !== '\n')
              输入流.下个字符()
            }
          }

          const 令牌 = {
            关键词: 值 => ({类型: '关键词', 值}),
            字符串: 值 => ({类型: '字符串', 值}),
            整数: 值 => ({类型: '整数', 值}),
            类型: 值 => ({类型: '类型', 值}),
            变量: 名称 => ({类型: '变量', 名称}),
            标点符号: 值 => ({类型: '标点符号', 值}),
            运算符: 值 => ({类型: '运算符', 值})
          }

          const 读取 = {
            下个令牌: () => {
              读取.期间(是否.空白字符)
              if (输入流.结束()) return null
              const 字符 = 输入流.观测()

              switch (字符) {
                case '~': {
                  跳过.批注()
                  return 读取.下个令牌()
                }
                case '“': {
                  return 读取.字符串()
                }
                case '「': {
                  return 读取.引用变量()
                }
              }

              if (是否.运算符(字符)) return 令牌.运算符(读取.期间(是否.运算符))
              if (是否.关键词(字符)) return 令牌.关键词(读取.期间(是否.关键词))
              if (是否.类型(字符)) return 令牌.类型(读取.期间(是否.类型))
              if (是否.整数(字符)) return 读取.整数()
              if (是否.变量(字符)) return 读取.变量()
              if (是否.标点符号(字符)) return 令牌.标点符号(输入流.下个字符())

              输入流.异常(`意外的令牌 「${字符}」`)
            },
            期间: 断言 => {
              let 字符 = ''
              while (!输入流.结束() && 断言(输入流.观测()))
                字符 += 输入流.下个字符()
              return 字符
            },
            捕捉: 结束字符 => {
              let 已捕捉 = false, 捕捉字符 = ''
              输入流.下个字符()
              while (!输入流.结束()) {
                const 字符 = 输入流.下个字符()
                if (已捕捉) {
                  捕捉字符 += 字符
                  已捕捉 = false
                } else if (字符 === '\\') {
                  已捕捉 = true
                } else if (字符 === 结束字符) {
                  break
                } else {
                  捕捉字符 += 字符
                }
              }
              return 捕捉字符
            },
            引用变量: () => 令牌.变量(读取.捕捉('」')),
            变量: () => 令牌.变量(读取.期间(是否.变量)),
            字符串: () => 令牌.字符串(读取.捕捉('”')),
            整数: () => 令牌.整数(读取.期间(是否.整数))
          }

          const 观测 = () => 当前 || (当前 = 读取.下个令牌())

          const 下个令牌 = () => {
            const 令牌 = 当前
            当前 = null
            return 令牌 || 读取.下个令牌()
          }

          const 结束 = () => 观测() == null

          return {下个令牌, 观测, 结束, 是否匹配: 是否, 异常: 输入流.异常}
        }

        const 解析 = 令牌流 => {
          const 令牌 = {
            程序: 程序体 => ({
              '类型': '程序',
              '描述': {
                '程序体': 程序体
              }
            }),
            声明变量: (名称, 类型, 值) => ({
              '类型': '声明变量',
              '描述': {
                '名称': 名称,
                '类型': 类型,
                '值': 值
              }
            }),
            调用表达式: (名称, 参数) => ({
              '类型': '调用表达式',
              '描述': {
                '调用': 名称,
                '参数': 参数
              }
            }),
            获取变量: 名称 => ({
              '类型': '获取变量',
              '描述': {
                '名称': 名称
              }
            }),
            字符串: 值 => ({
              '类型': '字符串',
              '描述': {
                '值': 值
              }
            })
          }

          const 是否 = {
            标点符号: 字符 => {
              const 令牌 = 令牌流.观测()
              return 令牌 && 令牌.类型 === '标点符号' && (!字符 || 令牌.值 === 字符) && 令牌
            },
            类型: 字符 => {
              const 令牌 = 令牌流.观测()
              return 令牌 && 令牌.类型 === '类型' && (!字符 || 令牌.值 === 字符) && 令牌
            },
            关键词: 关键词 => {
              const 令牌 = 令牌流.观测()
              return 令牌 && 令牌.类型 === '关键词' && (!关键词 || 令牌.值 === 关键词) && 令牌
            },
            运算符: 运算符 => {
              const 令牌 = 令牌流.观测()
              return 令牌 && 令牌.类型 === '运算符' && (!运算符 || 令牌.值 === 运算符) && 令牌
            }
          }

          const 跳过 = {
            标点符号: 字符 => {
              if (是否.标点符号(字符)) 令牌流.下个令牌()
              else 令牌流.异常(`期望标点符号 「${字符}」`)
            },
            类型: 字符 => {
              if (是否.类型(字符)) 令牌流.下个令牌()
              else 令牌流.异常(`期望类型 「${字符}」`)
            }
          }

          const 或许 = {
            调用: 表达式 => 表达式(),
            嵌套: (当前节点, 父节点) => 当前节点
          }

          const 语法分析 = {
            顶层: () => {
              const 语法树 = Array()
              while (!令牌流.结束()) {
                语法树.push(语法分析.表达式())
              }
              return 令牌.程序(语法树)
            },
            表达式: () => 或许.调用(() => 或许.嵌套(语法分析.原子(), 0)),
            原子: () => 或许.调用(() => {
              if (是否.标点符号('。')) 令牌流.下个令牌()

              if (是否.关键词('我有一个') && 令牌流.下个令牌()) {
                const 类型令牌 = 令牌流.下个令牌()
                const 变量令牌 = 令牌流.下个令牌()
                const 运算符令牌 = 令牌流.下个令牌()
                const 令牌 = 令牌流.下个令牌()

                if (类型令牌.类型 === '类型' &&
                  变量令牌.类型 === '变量' &&
                  运算符令牌.类型 === '运算符' &&
                  运算符令牌.值 === '等于' &&
                  (令牌.类型 === '整数' || 令牌.类型 === '字符串')
                ) return 语法分析.声明变量(变量令牌.名称, 类型令牌.值, 令牌.值)
              }

              const 令牌 = 令牌流.下个令牌()
              if (令牌.类型 === '变量') {
                const 名称 = 令牌.名称
                const 参数 = Array()
                const 标点符号令牌 = 令牌流.下个令牌()
                if (标点符号令牌.值 === '：') {
                  while (!是否.标点符号('。')) {
                    const 令牌 = 令牌流.下个令牌()
                    if (令牌 === null) {
                      跳过.标点符号('。')
                      break
                    }
                    switch (令牌.类型) {
                      case '变量':
                        参数.push(语法分析.获取变量(令牌.名称))
                        break
                      case '字符串':
                        参数.push(语法分析.字符串(令牌.值))
                        break
                      default:
                        语法分析.意外()
                    }
                  }
                  令牌流.下个令牌()
                  return 语法分析.调用表达式(名称, 参数)
                }
              }

              语法分析.意外()
            }),
            获取变量: 名称 => 令牌.获取变量(名称),
            字符串: 值 => 令牌.字符串(值),
            调用表达式: (名称, 参数) => 令牌.调用表达式(名称, 参数),
            声明变量: (名称, 类型, 值) => {
              if ((类型 === '整数' && !令牌流.是否匹配.整数(值))) {
                令牌流.异常(`需要整数，但得到 「${值}」`)
              }
              if ((类型 === '字符串' && 令牌流.是否匹配.整数(值))) {
                令牌流.异常(`需要字符串，但得到 「${值}」`)
              }
              return 令牌.声明变量(名称, 类型, 值)
            },
            意外: () => 令牌流.异常(`意外的令牌 「${令牌流.观测().值 || 令牌流.观测().名称 || 令牌流.观测().类型}」`)
          }

          return 语法分析.顶层()
        }

        const 执行 = (语句, 环境) => {
          switch (语句.类型) {
            case '程序':
              let 返回值 = false
              语句.描述.程序体.forEach(语句 => {
                返回值 = 执行(语句, 环境)
              })
              return 返回值
            case '字符串':
              return 语句.描述.值
            case '获取变量':
              return 环境.获取(语句.描述.名称)
            case '声明变量':
              return 环境.设置(语句.描述.名称, 语句.描述.值)
            case '调用表达式':
              if (typeof 环境.变量表[语句.描述.调用] !== 'function') throw new Error(`不是一个方法 「${语句.描述.调用}」`)
              let 参数 = String()
              语句.描述.参数.forEach(参数表达式 => 参数 += 执行(参数表达式, 环境))
              return 环境.变量表[语句.描述.调用].call(null, 参数)
            default:
              throw new Error(`无法执行 「${语句.类型}」`)
          }
        }

        function 环境() {
          this.变量表 = Object.create(null)
        }

        环境.prototype = {
          获取: function (名称) {
            if (名称 in this.变量表)
              return this.变量表[名称] || '空'
            throw new Error(`未定义变量 「${名称}」`)
          },
          设置: function (名称, 值) {
            return this.变量表[名称] = 值
          }
        }

        const 默认环境 = new 环境()

        const 内置方法 = {
          控制台输出: 信息 => {
            document.querySelector('#console').value += `${信息}\n`
          },
          弹窗: 信息 => {
            alert(信息 || '空')
          }
        }

        for (let 方法 in 内置方法) {
          默认环境.设置(方法, 内置方法[方法])
        }

        document.querySelector('#ast').value = JSON.stringify(解析(令牌流(输入流(脚本))), null, '  ')

        执行(解析(令牌流(输入流(脚本))), 默认环境)
      }

      window.onerror = function (msg) {
        alert(msg)
        return true
      }
    </script>

  </body>

</html>